/*
 * Copyright (C) 2010-2011 sunjumper@163.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package mfinder.config;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import mfinder.ActionFactory;
import mfinder.impl.DefaultActionFactory;
import mfinder.impl.Injector;
import mfinder.impl.Injector.Injection;
import mfinder.util.ClassUtil;
import mfinder.util.CollectionUtil;
import mfinder.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * 启动mfinder容器的入口配置类。通过Configuration类加载mfinder的配置文件（默认为mfinder-1.5.xml）初始化ActionFactory
 * 及加载相应的属性配置，最终得到ActionFactory具体实例。
 * <p>
 * 如果mfinder.xml中未指明ActionFactory的具体实现类，则默认使用{@link DefaultActionFactory }。
 * </p>
 * <p>
 * 通常如下使用：
 * <code><blockquote><pre>
 * Configuration config = new Configuration().load();
 * ActionFactory factory = config.getFactory();
 * factory...
 * </pre></blockquote></code>
 * </p>
 *
 * @see #getFactory()
 */
public class Configuration {

    /* 日志记录 */
    private static final Logger LOG = LoggerFactory.getLogger(Configuration.class);

////////////////////////////////////////////////////////////////////////////////
//                           xml配置文件元素                                   //
////////////////////////////////////////////////////////////////////////////////
    /** 默认配置文件的名称 */
    public static final String DEFAULT_FILE = "mfinder-1.5.xml";

    /** 配置文件中表示ActionFactory的标签名 */
    public static final String ACTION_FACTORY = "action-factory";

    /** 配置文件中表示属性的标签属性 */
    public static final String PROPERTY = "property";

    /** 配置文件中表示常量的标签名，适配1.4版本 */
    @Deprecated
    public static final String CONSTANT = "constant";

    /** 配置文件中表示类名称的标签属性 */
    public static final String CLASS = "class";

    /** 配置文件中表示值的标签属性 */
    public static final String VALUE = "value";

    /** 配置文件中表示名称的标签属性 */
    public static final String NAME = "name";

    /** 配置文件中表示拦截器的标签名 */
    public static final String INTERCEPTOR = "interceptor";

    /** 配置文件中表示拦截栈的标签名 */
    public static final String INTERCEPTOR_STACK = "interceptor-stack";

    /** 配置文件中表示结果类型的标签名 */
    public static final String RESULT_TYPE = "result-type";

    /** 配置文件中表示结果对象的标签名 */
    public static final String RESULT = "result";

    /** 配置文件中表示Action的标签名 */
    public static final String ACTION = "action";

    /** 配置文件中表示path的标签名 */
    public static final String PATH = "path";

    /** 配置文件中表示包含其它配置的标签名 */
    public static final String INCLUDE = "include";

    /** 配置文件中表示文件名称的标签属性 */
    public static final String FILE = "file";

    /** 配置文件中表示扫描组件的标签名 */
    public static final String COMPONENT_SCAN = "component-scan";

    /** 配置文件中表示包名称的标签属性 */
    public static final String PACKAGE = "package";

    /** 配置文件中表示不包含的标签属性 */
    public static final String EXCLUDE = "exclude";

    /** factory */
    private ActionFactory<?> factory;

    /** 自动检测引入的包名  */
    protected Set<Class<?>> scanComponents = new LinkedHashSet<Class<?>>();

    /** 排除的包或类 */
    protected Set<String> excludeComponents = new HashSet<String>();

    /**
     * 加载默认配置文件{@link #DEFAULT_FILE}。
     *
     * @return 此配置对象的引用。
     *
     * @throws ConfigurationException 如果发生配置错误。
     */
    public Configuration load() throws ConfigurationException {
        try {
            load(getResourceAsStream(DEFAULT_FILE));
            LOG.info("Configuration initiate " + Configuration.this + " from /" + DEFAULT_FILE);
        } catch (ConfigurationException e) {
            LOG.error("Could not load mfinder settings from /" + DEFAULT_FILE);
            throw e;
        }
        return this;
    }

    /**
     * 从指定资源获取流数据。
     *
     * @param name 资源文件名。
     *
     * @return 流数据。
     */
    private static InputStream getResourceAsStream(String name) {
        return Configuration.class.getClassLoader().getResourceAsStream(name);
    }

    /**
     * 打印分隔符。
     *
     * @param bool 是否打印。
     */
    private static void printSeparator(boolean bool) {
        if (bool)
            System.out.println("--------------------------------------------------------------------------------");
    }

    /**
     * 加载配置。
     *
     * @param stream InputStream。
     *
     * @return 此配置对象的引用。
     *
     * @throws ConfigurationException 如果发生配置错误。
     */
    public Configuration load(InputStream stream) throws ConfigurationException {
        try {
            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream);
            //root node : <mfinder>
            Element root = doc.getDocumentElement();

            List<Element> list = null;
            int length = 0;

            //ActionFactory
            list = getChildNodesByTagName(root, ACTION_FACTORY);
            if ((length = list.size()) == 1) {
                Element e = list.get(0);
                String cls = e.getAttribute(CLASS);
                //create ActionFactory
                factory = StringUtil.isNull(cls)
                        ? new DefaultActionFactory()
                        : (ActionFactory) Class.forName(cls).newInstance();

                //action factory's properties
                list = getChildNodesByTagName(e, PROPERTY);
                //inject factory's properties
                setProperties(factory, list);
            } else if (length > 1) {
                throw new ConfigurationException("More than one <" + ACTION_FACTORY + "> tag in setting.", null);
            }

            //adapt for 1.0
            /*
            if (factory == null) {
            //constant
            list = doc.getElementsByTagName(CONSTANT);
            length = list.getLength();
            if (length > 0) {
            LOG.warn("Deprecated tag <" + CONSTANT + ">, use <" + ACTION_FACTORY + "> instead.");
            } else {
            //factory = new DefaultActionFactory();
            for (int i = 0; i < length; i++) {
            e = (Element) list.item(i);
            String name = e.getAttribute(NAME);
            //加载常量
            String val = e.getAttribute(VALUE);
            if (StringUtil.isNotNull(val)) {
            variables.put(name, val);
            } else {
            String cls = e.getAttribute(CLASS);
            if (StringUtil.isNotNull(cls)) {
            variables.put(name, cls);
            }
            }
            }
            String prop = variables.get("ActionFactory");
            //create ActionFactory
            factory = StringUtil.isNull(prop)
            ? new DefaultActionFactory()
            : (ActionFactory) Class.forName(prop).newInstance();

            //set properties for ActionFactory
            prop = variables.get("DefaultInterceptorStack");
            if (StringUtil.isNotNull(prop))
            factory.setDefaultInterceptorStack(prop);
            prop = variables.get("DefaultResultType");
            if (StringUtil.isNotNull(prop))
            factory.setDefaultResultType(prop);
            }
            }
             */
            //if no ActionFactory setting
            if (factory == null) {
                LOG.info("No <{}> tag setting, use [{}]", ACTION_FACTORY, DefaultActionFactory.class);
                factory = new DefaultActionFactory();
            }

            LOG.info("Create MFinder ActionFactory : " + factory);

            //parse "<component-scan>"
            scanComponents(root);

            //give subclasses a chance to prepare factory
            afterActionFactoryCreation(factory);

            printSeparator(!list.isEmpty());

            //依次添加interceptor、interceptorStack、result-type、result、action。
            addActionFactoryElements(root);

            //include
            list = getChildNodesByTagName(root, INCLUDE);
            //length = list.size();
            Collection coll = new HashSet<String>();
            for (Element e : list) {
                //add included files, use a hash set to avoid circular reference
                addIncludedProperties(e.getAttribute(FILE), coll);
            }
        } catch (ConfigurationException e) {
            throw e;
        } catch (Exception e) {
            throw new ConfigurationException("Could not load or parse properties from input stream", e);
        } finally {
            clear();
        }
        return this;
    }

    /**
     * 解析"<component-scan>"并添加自动检索的类。
     *
     * @param root 文档根节点。
     *
     * @throws ClassNotFoundException 如果无法定位类。
     */
    private void scanComponents(Element root) throws ClassNotFoundException {
        List<Element> list = getChildNodesByTagName(root, COMPONENT_SCAN);

        printSeparator(!list.isEmpty());

        //separator
        char[] sep = {',', ';'};
        for (Element e : list) {
            String pkg = e.getAttribute(PACKAGE);
            String exclude = e.getAttribute(EXCLUDE);
            LOG.info("Scan components package : [{}], exclude : [{}]", pkg, exclude);
            scanComponents.addAll(ClassUtil.getClasses(CollectionUtil.stringToCollection(pkg, null, sep).toArray(new String[0])));
            if (StringUtil.isNotNull(exclude))
                excludeComponents.addAll(CollectionUtil.stringToCollection(exclude, new HashSet<String>(), sep));
        }
        //filter the scan classes
        Iterator<Class<?>> it = scanComponents.iterator();
        out:
        while (it.hasNext()) {
            Class cls = it.next();
            //exclude interface, no public class and no default
            if (cls.isInterface()
                    || Modifier.isAbstract(cls.getModifiers())
                    || !Modifier.isPublic(cls.getModifiers())) {
                it.remove();
                continue out;
            } //去除无默认public空构造方法的类
            else {
                //获取对象的public构造方法
                Constructor<?>[] cs = cls.getConstructors();
                if (cs.length == 0) {
                    it.remove();
                    continue out;
                } else {
                    boolean hasEmptyConstructor = false;
                    for (Constructor<?> c : cs) {
                        //空构造方法
                        if (c.getGenericParameterTypes().length == 0) {
                            hasEmptyConstructor = true;
                            break;
                        }
                    }
                    if (!hasEmptyConstructor) {
                        it.remove();
                        continue out;
                    }
                }
            }
            //过滤excludeComponents中所包含的
            if (!excludeComponents.isEmpty()) {
                String clsPkg = cls.getPackage().getName();
                //直接排除包或类
                if (excludeComponents.contains(cls.getName()) || excludeComponents.contains(clsPkg)) {
                    it.remove();
                    continue out;
                } else {
                    //排除匹配的包
                    for (String exclude : excludeComponents) {
                        if (clsPkg.startsWith(exclude)) {
                            it.remove();
                            continue out;
                        }
                    }
                }
            }
        }
//        System.out.println("****************************************");
//        System.out.println(excludeComponents);
//        Iterator<Class<?>> __it = scanComponents.iterator();
//        while (__it.hasNext())
//            System.out.println(__it.next());
//        System.out.println("****************************************");
    }

    /**
     * 添加包含的配置文件"<include>", 用一个集合类判断并避免循环引用。
     *
     * @param name 配置文件名称。
     * @param coll 指定的集合。
     */
    private void addIncludedProperties(String name, Collection<String> coll) {
        LOG.info("Load included file : " + name);
        printSeparator(true);

        InputStream stream = getResourceAsStream(name);
        if (stream == null) {
            throw new IllegalArgumentException("Can't load input stream from included file : " + name);
        }
        //if circular reference
        if (coll.contains(name)) {
            throw new IllegalArgumentException("Load circular reference file : " + name);
        }
        coll.add(name);

        try {
            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream);
            Element root = doc.getDocumentElement();

            //add properties
            addActionFactoryElements(root);

            //add include
            List<Element> list = getChildNodesByTagName(root, INCLUDE);
            for (Element e : list) {
                addIncludedProperties(e.getAttribute(FILE), coll);
            }

        } catch (Exception e) {
            throw new ConfigurationException("Could not load or parse properties from included file : " + name, e);
        } finally {
            if (stream != null)
                try {
                    stream.close();
                } catch (IOException e) {
                    LOG.error("Fail to close input stream " + name, e);
                }
        }
    }

    /**
     * 依次添加interceptor、interceptorStack、result-type、result、action。
     *
     * @param root 文档根节点。
     *
     * @throws ClassNotFoundException 如果没有找到具有指定名称的类。
     * @throws IllegalAccessException 如果底层方法不可访问。
     * @throws InstantiationException 如果实例化失败。
     */
    private void addActionFactoryElements(Element root) throws ClassNotFoundException,
            IllegalAccessException, IntrospectionException, InvocationTargetException {
        if (factory instanceof DefaultActionFactory) {
            DefaultActionFactory defaultFactory = (DefaultActionFactory) this.factory;

            //排除配置文件中指定的类
            Set<String> specified = new HashSet<String>();

            List<Element> list = null;
            //interceptor
            list = getChildNodesByTagName(root, INTERCEPTOR);
            for (Element e : list) {
                String cls = e.getAttribute(CLASS);
                specified.add(cls);
                Object obj = factory.getObjectFactory().newInstance(Class.forName(cls));
                //set property nodes
                setProperties(obj, getChildNodesByTagName(e, PROPERTY));
                //add interceptor
                defaultFactory.addInterceptors(obj);
            }
            //scan interceptors
            for (Class<?> cls : scanComponents) {
                if (!specified.contains(cls.getName()))
                    defaultFactory.addInterceptors(cls);
            }
            //clear
            specified.clear();
            printSeparator(!defaultFactory.getInterceptors().isEmpty());

            //interceptor-stack
            list = getChildNodesByTagName(root, INTERCEPTOR_STACK);
            for (Element e : list) {
                String cls = e.getAttribute(CLASS);
                specified.add(cls);
                //System.out.println(INTERCEPTOR_STACK + " : " + e.getAttribute(CLASS));
                //add interceptor stacks
                defaultFactory.addInterceptorStacks(factory.getObjectFactory().newInstance(Class.forName(cls)));
            }
            //scan interceptorStacks
            for (Class<?> cls : scanComponents) {
                if (!specified.contains(cls.getName()))
                    defaultFactory.addInterceptorStacks(cls);
            }
            //clear
            specified.clear();
            printSeparator(!defaultFactory.getInterceptorStacks().isEmpty());

            //result-type
            list = getChildNodesByTagName(root, RESULT_TYPE);
            for (Element e : list) {
                String cls = e.getAttribute(CLASS);
                specified.add(cls);
                Object obj = factory.getObjectFactory().newInstance(Class.forName(cls));
                //set property nodes
                setProperties(obj, getChildNodesByTagName(e, PROPERTY));
                //add result types
                defaultFactory.addResultTypes(obj);
            }
            //scan resultTypes
            for (Class<?> cls : scanComponents) {
                if (!specified.contains(cls.getName()))
                    defaultFactory.addResultTypes(cls);
            }
            //clear
            specified.clear();
            printSeparator(!defaultFactory.getResultTypes().isEmpty());

            //result
            list = getChildNodesByTagName(root, RESULT);
            for (Element e : list) {
                String cls = e.getAttribute(CLASS);
                specified.add(cls);
                //add results
                Object obj = factory.getObjectFactory().newInstance(Class.forName(cls));
                //set property nodes
                setProperties(obj, getChildNodesByTagName(e, PROPERTY));
                defaultFactory.addResults(obj);
            }
            //scan results
            for (Class<?> cls : scanComponents) {
                if (!specified.contains(cls.getName()))
                    defaultFactory.addResults(cls);
            }
            //clear
            specified.clear();
            printSeparator(!defaultFactory.getResults().isEmpty());

            //action
            list = getChildNodesByTagName(root, ACTION);
            for (Element action : list) {
                String cls = action.getAttribute(CLASS);
                specified.add(cls);
                //System.out.println(e.getAttribute(CLASS));
                Object obj = factory.getObjectFactory().newInstance(Class.forName(cls));

                //action <property> nodes
                List<Element> propnodes = getChildNodesByTagName(action, PROPERTY);

                //common class properties
                Map<String, PropertyDescriptor> supports = Injector.getSupportedProperties(obj.getClass());

                //待注入相应class的Action属性列表
                Map<String, Injection> classInjections = getInjections(propnodes, obj, supports);

                //properties for action class
                if (classInjections.size() > 0) {
                    Injector.putClassProperties(obj.getClass(), classInjections.values().toArray(new Injection[classInjections.size()]));
                    //Action对象属性注入
                    Injector.injectObject(obj);
                }

                //<path> nodes
                List<Element> pathnodes = getChildNodesByTagName(action, PATH);
                for (Element path : pathnodes) {
                    //path <property> nodes
                    propnodes = getChildNodesByTagName(path, PROPERTY);
                    //待注入相应path的Action属性列表
                    Map<String, Injection> pathInjections = getInjections(propnodes, obj, supports);
                    if (pathInjections.size() > 0) {
                        //属性包含class注入属性和path注入属性
                        Map<String, Injection> allInjections = new LinkedHashMap<String, Injection>(classInjections.size() + pathInjections.size());
                        allInjections.putAll(classInjections);
                        allInjections.putAll(pathInjections);
                        //System.out.println(path.getAttribute(NAME) + "," + allInjections);
                        Injector.putActionProperties(path.getAttribute(NAME), allInjections.values().toArray(new Injection[allInjections.size()]));
                    }
                }
                //add actions
                defaultFactory.addActions(obj);
            }
            //scan results
            for (Class<?> cls : scanComponents) {
                if (!specified.contains(cls.getName()))
                    defaultFactory.addActions(cls);
            }
            //clear
            specified.clear();
            printSeparator(!defaultFactory.getActions().isEmpty());
        }
    }

    /**
     * 由标签"<property>"集合获取指定对象所支持注入属性的映射集合。
     *
     * @param propnodes 标签<property>集合。
     * @param obj 指定对象。
     * @param supports 指定所支持的属性集。
     *
     * @return 指定对象所支持注入属性的映射集合。
     */
    private static Map<String, Injection> getInjections(List<Element> propnodes, Object obj,
            Map<String, PropertyDescriptor> supports) {
        String cls = obj.getClass().getName();
        Map<String, Injection> injections = new LinkedHashMap<String, Injection>(propnodes.size());
        for (Element prop : propnodes) {
            String pName = prop.getAttribute(NAME);
            PropertyDescriptor pd = supports.get(pName);
            if (pd == null) {
                LOG.error("Not supported property [{}] in [{}]", pName, cls);
            } else {
                Object value = Injector.stringToObject(prop.getAttribute(VALUE), pd.getPropertyType());
                if (value == null) {
                    LOG.warn("Not supported property [{}] for type [{}] in " + cls, pName, pd.getPropertyType());
                } else {
                    if (null != injections.put(pName, new Injection(pd.getWriteMethod(), value))) {
                        LOG.info("Override property [{}] value [{}] in " + cls, pName, prop.getAttribute(VALUE));
                    }
                }
            }
        }
        return injections;
    }

    /**
     * 注入指定对象的属性。
     *
     * @param obj 指定的对象。
     * @param propnodes 属性名称和字符串值的节点列表。
     *
     * @throws IntrospectionException 如果在内省期间发生异常。
     * @throws IllegalAccessException 如果底层方法不可访问。
     * @throws InvocationTargetException 如果底层方法抛出异常。
     */
    private static void setProperties(Object obj, List<Element> propnodes) throws
            IntrospectionException, IllegalAccessException, InvocationTargetException {
        String cls = obj.getClass().getName();
        Map<String, PropertyDescriptor> supports = Injector.getSupportedProperties(obj.getClass());
        Map<String, Object> injections = new LinkedHashMap<String, Object>(propnodes.size());
        for (Element prop : propnodes) {
            String pName = prop.getAttribute(NAME);
            PropertyDescriptor pd = supports.get(pName);
            if (pd == null) {
                LOG.error("Not supported property [{}] in [{}]", pName, cls);
            } else {
                Object value = Injector.stringToObject(prop.getAttribute(VALUE), pd.getPropertyType());
                if (null != injections.put(pName, value)) {
                    LOG.info("Override property [{}] value [{}] in " + cls, pName, prop.getAttribute(VALUE));
                }
            }
        }
        //属性注入
        for (Map.Entry<String, Object> e : injections.entrySet()) {
            supports.get(e.getKey()).getWriteMethod().invoke(obj, e.getValue());
        }
        supports.clear();
    }

    /**
     * 获取指定父节点和节点名称的子节点集合。
     *
     * @param parent 指定的父节点。
     * @param name 指定的节点名称
     *
     * @return 子节点集合。
     */
    private static List<Element> getChildNodesByTagName(Element parent, String name) {
        List<Element> eles = new ArrayList<Element>();
        for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
            //System.out.println(child.getNodeName() + "," + child.getNodeType());
            if (Node.ELEMENT_NODE == child.getNodeType() && name.equals(child.getNodeName())) {
                eles.add((Element) child);
            }
        }
        return eles;
    }
////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 返回ActionFactory。
     *
     * @param <T> Action工厂对象的类型。
     *
     * @return ActionFactory。
     */
    public <T extends ActionFactory<?>> T getFactory() {
        return (T) factory;
    }

    /**
     * 用于子类继承, 在初始化ActionFactory前执行设置其一些特定的操作。
     * 默认情况下不做任何处理。
     *
     * @param factory 未初始化的{@link ActionFactory}。
     */
    protected void afterActionFactoryCreation(ActionFactory<?> factory) {
    }

    /**
     * 清除加载的类、属性和常量。
     */
    public void clear() {
        scanComponents.clear();
        excludeComponents.clear();
    }
}
